package info.openlvb.openface.server.ldap;

import gnu.crypto.util.Base64;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.StringTokenizer;

public class VerifPassword {

	private static MessageDigest sha = null;
	private static byte[] digest = null;
	private static boolean result = false;
	private static String passwd = null;
	private static SmbLMPassword smbpass = new SmbLMPassword();
	private static final int SHA_LENGTH = 4;

	/**
	 * Analyse le mot de passe saisi pour connaitre l'algorithme de cryptage et faire appel
	 * à la bonne méthode de vérification du mot de passe saisi et celui stocké dans le LDAP.
	 *
	 * @param  clearPasswordBytes  Le mot de passe saisi.
	 *		   sshaPasswordString  Le mot de passe récupéré dans le LDAP.
	 * @return  True  : si il y a concordance des mots de passe.
	 * 			False : si il n'y a pas de concordance.              
	 */
	public static boolean verifPwd(String clearPWD, String cryptPWD){
		if(cryptPWD.contains("{")){
			String tmpString = cryptPWD.substring(1, cryptPWD.length());
			StringTokenizer st = new StringTokenizer(tmpString,"}");
			String type = st.nextToken();
			if(type.equals("SSHA") && st.hasMoreTokens()){
				try {
					result=compareSSHAPassword(clearPWD.getBytes(),cryptPWD);
				} catch (NoSuchAlgorithmException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				} catch (ParseException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			else if (type.equals("SHA") && st.hasMoreTokens()) {
				try {
					result=comparePassword(clearPWD, cryptPWD, "SHA");
				} catch (NoSuchAlgorithmException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			else if (type.equals("MD5") && st.hasMoreTokens()) {
				try {
					result=comparePassword(clearPWD, cryptPWD, "MD5");
				} catch (NoSuchAlgorithmException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		else if(cryptPWD.equals(clearPWD)){
			result=true;
		}
		else if(cryptPWD.equals(smbpass.transform(clearPWD))){
			result=true;
		}
		return result;

	}

	/**
	 * Compare le mot de passe saisi crypté avec l'algorithme "method" et le mot de passe
	 * stocké dans le LDAP
	 *
	 * @param  clearPasswordBytes  Le mot de passe saisi sous de forme de tableau de byte.
	 *		   sshaPasswordString  Le mot de passe récupéré dans le LDAP
	 *		   method			   L'algorithme (MD5 ou SHA) avec lequel doit être crypte 
	 *							   clearPasswordBytes.
	 * @return  True  : si il y a concordance des mots de passe.
	 * 			False : si il n'y a pas de concordance.
	 *
	 * @throws  ParseException  If a problem occurs while attempting to decode the
	 *                          provided data.
	 *          NoSuchAlgorithmException 
	 *          				L'algorithme n'est pas connu                
	 */
	private static boolean comparePassword(String clearPWD, String cryptPWD, String method)
	throws NoSuchAlgorithmException, ParseException
	{
		//Cryptage du mot de passe
		sha = MessageDigest.getInstance (method);
		sha.reset();
		try {
			sha.update(clearPWD.getBytes("utf-8"));
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		digest = sha.digest();
		passwd = new String(Base64.encode(digest));
		//mise sur la meme base que le ci-dessus
		if(cryptPWD.equals(passwd)){
			return true;
		}
		return false;
	}

	/**
	 * Compare le mot de passe saisi crypté avec la methode SSHA et le mot de passe
	 * stocké dans le LDAP
	 *
	 * @param  clearPasswordBytes  Le mot de passe saisi sous de forme de tableau de byte.
	 *		   sshaPasswordString  Le mot de passe récupéré dans le LDAP
	 * @return  True  : si il y a concordance des mots de passe.
	 * 			False : si il n'y a pas de concordance.
	 *
	 * @throws  ParseException  If a problem occurs while attempting to decode the
	 *                          provided data.
	 *          NoSuchAlgorithmException 
	 *          				L'algorithme n'est pas connu                
	 */
	private static boolean compareSSHAPassword(byte[] clearPasswordBytes,
			String sshaPasswordString)
	throws NoSuchAlgorithmException, ParseException
	{
		// Base64-decode the value of the encoded password string and break it up
		// into the SHA-1 digest and salt portions.
		byte[] digestPlusSalt = base64Decode(sshaPasswordString.substring(6));
		byte[] saltBytes      = new byte[4];
		byte[] digestBytes    = new byte[digestPlusSalt.length - 4];
		System.arraycopy(digestPlusSalt, 0, digestBytes, 0, digestBytes.length);
		System.arraycopy(digestPlusSalt, digestBytes.length, saltBytes, 0,
				saltBytes.length);
		
		// Create a byte array that appends the salt bytes to the clear-text
		// password bytes.
		byte[] passwordPlusSalt =
			new byte[clearPasswordBytes.length + saltBytes.length];
		System.arraycopy(clearPasswordBytes, 0, passwordPlusSalt, 0,
				clearPasswordBytes.length);
		System.arraycopy(saltBytes, 0, passwordPlusSalt, clearPasswordBytes.length,
				saltBytes.length);

		// Get a message digest that can generate SHA-1 hashes and use it to digest
		// the password plus the salt.
		MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
		byte[] passwordPlusSaltHash = sha1Digest.digest(passwordPlusSalt);

		// If the provided password is correct, then the contents of the digestBytes
		// array will match the contents of the passwordPlusSaltHash array.
		return Arrays.equals(digestBytes, passwordPlusSaltHash);
	}

	/**
	 * Decodes the provided set of base64-encoded data.
	 *
	 * @param  encodedData  The base64-encoded data to decode.
	 *
	 * @return  The decoded raw data.
	 *
	 * @throws  ParseException  If a problem occurs while attempting to decode the
	 *                          provided data.
	 */
	public static byte[] base64Decode(String encodedData)
	throws ParseException
	{
		// The encoded value must have  length that is a multiple of four bytes.
		int length = encodedData.length();
		if ((length % 4) != 0)
		{
			throw new ParseException("The encoded value had a length that was not " +
					"a multiple of four bytes ("+length+")", 0);
		}


		ByteBuffer buffer = ByteBuffer.allocate(length);
		for (int i=0; i < length; i += 4)
		{
			boolean append = true;
			int     value  = 0;

			for (int j=0; j < 4; j++)
			{
				switch (encodedData.charAt(i+j))
				{
				case 'A':
					value <<= 6;
					break;
				case 'B':
					value = (value << 6) | 0x01;
					break;
				case 'C':
					value = (value << 6) | 0x02;
					break;
				case 'D':
					value = (value << 6) | 0x03;
					break;
				case 'E':
					value = (value << 6) | 0x04;
					break;
				case 'F':
					value = (value << 6) | 0x05;
					break;
				case 'G':
					value = (value << 6) | 0x06;
					break;
				case 'H':
					value = (value << 6) | 0x07;
					break;
				case 'I':
					value = (value << 6) | 0x08;
					break;
				case 'J':
					value = (value << 6) | 0x09;
					break;
				case 'K':
					value = (value << 6) | 0x0A;
					break;
				case 'L':
					value = (value << 6) | 0x0B;
					break;
				case 'M':
					value = (value << 6) | 0x0C;
					break;
				case 'N':
					value = (value << 6) | 0x0D;
					break;
				case 'O':
					value = (value << 6) | 0x0E;
					break;
				case 'P':
					value = (value << 6) | 0x0F;
					break;
				case 'Q':
					value = (value << 6) | 0x10;
					break;
				case 'R':
					value = (value << 6) | 0x11;
					break;
				case 'S':
					value = (value << 6) | 0x12;
					break;
				case 'T':
					value = (value << 6) | 0x13;
					break;
				case 'U':
					value = (value << 6) | 0x14;
					break;
				case 'V':
					value = (value << 6) | 0x15;
					break;
				case 'W':
					value = (value << 6) | 0x16;
					break;
				case 'X':
					value = (value << 6) | 0x17;
					break;
				case 'Y':
					value = (value << 6) | 0x18;
					break;
				case 'Z':
					value = (value << 6) | 0x19;
					break;
				case 'a':
					value = (value << 6) | 0x1A;
					break;
				case 'b':
					value = (value << 6) | 0x1B;
					break;
				case 'c':
					value = (value << 6) | 0x1C;
					break;
				case 'd':
					value = (value << 6) | 0x1D;
					break;
				case 'e':
					value = (value << 6) | 0x1E;
					break;
				case 'f':
					value = (value << 6) | 0x1F;
					break;
				case 'g':
					value = (value << 6) | 0x20;
					break;
				case 'h':
					value = (value << 6) | 0x21;
					break;
				case 'i':
					value = (value << 6) | 0x22;
					break;
				case 'j':
					value = (value << 6) | 0x23;
					break;
				case 'k':
					value = (value << 6) | 0x24;
					break;
				case 'l':
					value = (value << 6) | 0x25;
					break;
				case 'm':
					value = (value << 6) | 0x26;
					break;
				case 'n':
					value = (value << 6) | 0x27;
					break;
				case 'o':
					value = (value << 6) | 0x28;
					break;
				case 'p':
					value = (value << 6) | 0x29;
					break;
				case 'q':
					value = (value << 6) | 0x2A;
					break;
				case 'r':
					value = (value << 6) | 0x2B;
					break;
				case 's':
					value = (value << 6) | 0x2C;
					break;
				case 't':
					value = (value << 6) | 0x2D;
					break;
				case 'u':
					value = (value << 6) | 0x2E;
					break;
				case 'v':
					value = (value << 6) | 0x2F;
					break;
				case 'w':
					value = (value << 6) | 0x30;
					break;
				case 'x':
					value = (value << 6) | 0x31;
					break;
				case 'y':
					value = (value << 6) | 0x32;
					break;
				case 'z':
					value = (value << 6) | 0x33;
					break;
				case '0':
					value = (value << 6) | 0x34;
					break;
				case '1':
					value = (value << 6) | 0x35;
					break;
				case '2':
					value = (value << 6) | 0x36;
					break;
				case '3':
					value = (value << 6) | 0x37;
					break;
				case '4':
					value = (value << 6) | 0x38;
					break;
				case '5':
					value = (value << 6) | 0x39;
					break;
				case '6':
					value = (value << 6) | 0x3A;
					break;
				case '7':
					value = (value << 6) | 0x3B;
					break;
				case '8':
					value = (value << 6) | 0x3C;
					break;
				case '9':
					value = (value << 6) | 0x3D;
					break;
				case '+':
					value = (value << 6) | 0x3E;
					break;
				case '/':
					value = (value << 6) | 0x3F;
					break;
				case '=':
					append = false;
					switch (j)
					{
					case 2:
						buffer.put((byte) ((value >>> 4) & 0xFF));
						break;
					case 3:
						buffer.put((byte) ((value >>> 10) & 0xFF));
						buffer.put((byte) ((value >>>  2) & 0xFF));
						break;
					}
					break;
				default:
					throw new ParseException("Invalid base64 character " +
							encodedData.charAt(i+j), i+j);
				}


				if (! append)
				{
					break;
				}
			}


			if (append)
			{
				buffer.put((byte) ((value >>> 16) & 0xFF));
				buffer.put((byte) ((value >>>  8) & 0xFF));
				buffer.put((byte) (value & 0xFF));
			}
			else
			{
				break;
			}
		}


		buffer.flip();
		byte[] returnArray = new byte[buffer.limit()];
		buffer.get(returnArray);
		return returnArray;
	}
}
